/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;

import com.android.dx.io.opcode.Opcodes;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.ExceptionWithContext;
import com.android.dx.util.FixedSizeList;
import com.android.dx.util.IndentingWriter;

/**
 * List of {@link DalvInsn} instances.
 */
public final class DalvInsnList extends FixedSizeList {

	/**
	 * The amount of register space, in register units, required for this code
	 * block. This may be greater than the largest observed register+ category
	 * because the method this code block exists in may specify arguments that
	 * are unused by the method.
	 */
	private final int regCount;

	/**
	 * Constructs and returns an immutable instance whose elements are identical
	 * to the ones in the given list, in the same order.
	 * 
	 * @param list
	 *            {@code non-null;} the list to use for elements
	 * @param regCount
	 *            count, in register-units, of the number of registers this code
	 *            block requires.
	 * @return {@code non-null;} an appropriately-constructed instance of this
	 *         class
	 */
	public static DalvInsnList makeImmutable(ArrayList<DalvInsn> list,
			int regCount) {
		int size = list.size();
		DalvInsnList result = new DalvInsnList(size, regCount);

		for (int i = 0; i < size; i++) {
			result.set(i, list.get(i));
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Constructs an instance. All indices initially contain {@code null}.
	 * 
	 * @param size
	 *            the size of the list
	 */
	public DalvInsnList(int size, int regCount) {
		super(size);
		this.regCount = regCount;
	}

	/**
	 * Gets the element at the given index. It is an error to call this with the
	 * index for an element which was never set; if you do that, this will throw
	 * {@code NullPointerException}.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which index
	 * @return {@code non-null;} element at that index
	 */
	public DalvInsn get(int n) {
		return (DalvInsn) get0(n);
	}

	/**
	 * Sets the instruction at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which index
	 * @param insn
	 *            {@code non-null;} the instruction to set at {@code n}
	 */
	public void set(int n, DalvInsn insn) {
		set0(n, insn);
	}

	/**
	 * Gets the size of this instance, in 16-bit code units. This will only
	 * return a meaningful result if the instructions in this instance all have
	 * valid addresses.
	 * 
	 * @return {@code >= 0;} the size
	 */
	public int codeSize() {
		int sz = size();

		if (sz == 0) {
			return 0;
		}

		DalvInsn last = get(sz - 1);
		return last.getNextAddress();
	}

	/**
	 * Writes all the instructions in this instance to the given output
	 * destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 */
	public void writeTo(AnnotatedOutput out) {
		int startCursor = out.getCursor();
		int sz = size();

		if (out.annotates()) {
			boolean verbose = out.isVerbose();

			for (int i = 0; i < sz; i++) {
				DalvInsn insn = (DalvInsn) get0(i);
				int codeBytes = insn.codeSize() * 2;
				String s;

				if ((codeBytes != 0) || verbose) {
					s = insn.listingString("  ", out.getAnnotationWidth(), true);
				} else {
					s = null;
				}

				if (s != null) {
					out.annotate(codeBytes, s);
				} else if (codeBytes != 0) {
					out.annotate(codeBytes, "");
				}
			}
		}

		for (int i = 0; i < sz; i++) {
			DalvInsn insn = (DalvInsn) get0(i);
			try {
				insn.writeTo(out);
			} catch (RuntimeException ex) {
				throw ExceptionWithContext.withContext(ex, "...while writing "
						+ insn);
			}
		}

		// Sanity check of the amount written.
		int written = (out.getCursor() - startCursor) / 2;
		if (written != codeSize()) {
			throw new RuntimeException("write length mismatch; expected "
					+ codeSize() + " but actually wrote " + written);
		}
	}

	/**
	 * Gets the minimum required register count implied by this instance. This
	 * includes any unused parameters that could potentially be at the top of
	 * the register space.
	 * 
	 * @return {@code >= 0;} the required registers size
	 */
	public int getRegistersSize() {
		return regCount;
	}

	/**
	 * Gets the size of the outgoing arguments area required by this method.
	 * This is equal to the largest argument word count of any method referred
	 * to by this instance.
	 * 
	 * @return {@code >= 0;} the required outgoing arguments size
	 */
	public int getOutsSize() {
		int sz = size();
		int result = 0;

		for (int i = 0; i < sz; i++) {
			DalvInsn insn = (DalvInsn) get0(i);

			if (!(insn instanceof CstInsn)) {
				continue;
			}

			Constant cst = ((CstInsn) insn).getConstant();

			if (!(cst instanceof CstBaseMethodRef)) {
				continue;
			}

			boolean isStatic = (insn.getOpcode().getFamily() == Opcodes.INVOKE_STATIC);
			int count = ((CstBaseMethodRef) cst)
					.getParameterWordCount(isStatic);

			if (count > result) {
				result = count;
			}
		}

		return result;
	}

	/**
	 * Does a human-friendly dump of this instance.
	 * 
	 * @param out
	 *            {@code non-null;} where to dump
	 * @param prefix
	 *            {@code non-null;} prefix to attach to each line of output
	 * @param verbose
	 *            whether to be verbose; verbose output includes lines for
	 *            zero-size instructions and explicit constant pool indices
	 */
	public void debugPrint(Writer out, String prefix, boolean verbose) {
		IndentingWriter iw = new IndentingWriter(out, 0, prefix);
		int sz = size();

		try {
			for (int i = 0; i < sz; i++) {
				DalvInsn insn = (DalvInsn) get0(i);
				String s;

				if ((insn.codeSize() != 0) || verbose) {
					s = insn.listingString("", 0, verbose);
				} else {
					s = null;
				}

				if (s != null) {
					iw.write(s);
				}
			}

			iw.flush();
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Does a human-friendly dump of this instance.
	 * 
	 * @param out
	 *            {@code non-null;} where to dump
	 * @param prefix
	 *            {@code non-null;} prefix to attach to each line of output
	 * @param verbose
	 *            whether to be verbose; verbose output includes lines for
	 *            zero-size instructions
	 */
	public void debugPrint(OutputStream out, String prefix, boolean verbose) {
		Writer w = new OutputStreamWriter(out);
		debugPrint(w, prefix, verbose);

		try {
			w.flush();
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}
}
